package com.jeremyfeinstein.socket;

import android.text.TextUtils;
import android.util.Log;

import com.github.nkzawa.emitter.Emitter;
import com.github.nkzawa.engineio.client.Transport;
import com.github.nkzawa.socketio.client.IO;
import com.github.nkzawa.socketio.client.Manager;
import com.github.nkzawa.socketio.client.Socket;
import com.google.gson.Gson;
import com.jeremyfeinstein.exception.YchsException;
import com.jeremyfeinstein.gson.SocketBean;
import com.jeremyfeinstein.listener.OnSocketListener;
import com.jeremyfeinstein.okhttputil.HSHostnameVerifier;
import com.jeremyfeinstein.okhttputil.HSTrustManager;
import com.jeremyfeinstein.utils.SwitchToMainThread;
import com.jeremyfeinstein.utils.YCHShttpAPI;

import org.json.JSONObject;

import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;


/**
 * Created by sunxunchao on 2019/1/14.
 */
public abstract class SimpleSocketClient {
    private static final String TAG = SimpleSocketClient.class.getSimpleName();
    private Socket mSocket;
    private String socketCookies;
    private Transport transport;
    private List<OnSocketListener> listeners = new ArrayList<>();

    private X509TrustManager easyTrustManager = new HSTrustManager();

    public SimpleSocketClient(String url) {
        try {
            this.socketCookies = YCHShttpAPI.getCookiesString();
            SSLContext s = SSLContext.getInstance("TLSv1.2");
            s.init(null, new TrustManager[]{easyTrustManager}, new SecureRandom());
            IO.setDefaultSSLContext(s);
            io.socket.client.IO.setDefaultSSLContext(s);
            HttpsURLConnection.setDefaultHostnameVerifier(new HSHostnameVerifier());

            mSocket = IO.socket(url);
            Log.d(TAG, "url: " + url);
            bindSocketIoCallbacks();
            bindCallbacks();
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }



    public void connect() {
        if (mSocket != null) {
            mSocket.connect();
        }
    }

    public void disconnect() {
        if (mSocket != null) {
            mSocket.disconnect();
        }
    }

    public boolean isConnected() {
        return mSocket != null && mSocket.connected();
    }

    public void close() {
        if (mSocket != null) {
            mSocket.io().off();
            mSocket.off();
            mSocket = null;
        }
    }

    public void sendMsg(final JSONObject jsonObject) {
        Log.d(TAG, "sendSocketMessage :" + jsonObject.toString());
        if (mSocket != null && mSocket.connected()) {
            Log.d(TAG, "sendSocketMessage: connected");
            mSocket.emit(Socket.EVENT_MESSAGE, jsonObject);
        } else {
            Log.d(TAG, "sendSocketMessage: disconnected or null");
        }
    }


    private void bindSocketIoCallbacks() {
        mSocket.io().on(Manager.EVENT_TRANSPORT, onTransportListener);
    }

    private void bindCallbacks() {
        mSocket.on(Socket.EVENT_CONNECT_ERROR, onConnectErrorListener)
                .on(Socket.EVENT_CONNECT_TIMEOUT, onConnectTimeoutListener)
                .on(Socket.EVENT_CONNECT, onConnectListener)
                .on(Socket.EVENT_MESSAGE, onMessageListener)
                .on(Socket.EVENT_DISCONNECT, onDisconnectListener)
                .on(Socket.EVENT_ERROR, onErrorListener);
    }

    private Emitter.Listener onTransportListener = new Emitter.Listener() {
        @Override
        public void call(Object... objects) {
            if (objects.length > 0 && objects[0] instanceof Transport) {
                transport = (Transport) objects[0];
                Log.d(TAG, "call: transport ==> " + transport.toString());
                bindTransport();
            }
        }
    };

    private void bindTransport() {
        if (transport != null) {
            transport.on(Transport.EVENT_REQUEST_HEADERS, onRequestHeaderListener);
            transport.on(Transport.EVENT_RESPONSE_HEADERS, onResponseHeaderListener);
        }
    }

    private Emitter.Listener onResponseHeaderListener = new Emitter.Listener() {
        @Override
        public void call(Object... args) {
            if (args.length > 0 && args[0] instanceof Map) {
                Log.d(TAG, "call: start cookies ==> " + socketCookies);
                final Map<String, String> headers = (Map<String, String>) args[0];
                String cookie = headers.get("Set-Cookie");
                if (!TextUtils.isEmpty(socketCookies) && !socketCookies.contains("io")) {
                    socketCookies += cookie + ";";
                }
                Log.d(TAG, "call: end cookies ==> " + socketCookies);
            }
        }
    };

    private Emitter.Listener onRequestHeaderListener = new Emitter.Listener() {
        @Override
        public void call(Object... args) {
            if (args.length > 0 && args[0] instanceof Map) {
                Map<String, String> header = (Map<String, String>) args[0];
                header.put("cookie", socketCookies);
                Log.d(TAG, "call: request ==> "  + socketCookies);
            }
        }
    };

    private Emitter.Listener onConnectErrorListener = new Emitter.Listener() {
        @Override
        public void call(final Object... objects) {
            Log.d(TAG, "call: connect error");
            SwitchToMainThread.getInstance().post(new Runnable() {
                @Override
                public void run() {
                    for (OnSocketListener listener : listeners) {
                        listener.onError(Code.CONNECT_ERROR, new YchsException(objects[0].toString()));
                    }
                }
            });
        }
    };

    private Emitter.Listener onConnectTimeoutListener = new Emitter.Listener() {
        @Override
        public void call(final Object... objects) {
            Log.d(TAG, "call: connect timeout");
            SwitchToMainThread.getInstance().post(new Runnable() {
                @Override
                public void run() {
                    for (OnSocketListener listener : listeners) {
                        listener.onError(Code.CONNECT_TIMEOUT, new YchsException(objects[0].toString()));
                    }
                }
            });
        }
    };
    private Emitter.Listener onConnectListener = new Emitter.Listener() {
        @Override
        public void call(Object... objects) {
            Log.d(TAG, "call: connect");
            SwitchToMainThread.getInstance().post(new Runnable() {
                @Override
                public void run() {
                    for (OnSocketListener listener : listeners) {
                        listener.onConnectSuccess();
                    }
                }
            });
        }
    };

    private Emitter.Listener onDisconnectListener = new Emitter.Listener() {
        @Override
        public void call(final Object... objects) {
            Log.d(TAG, "call: disconnect");
            SwitchToMainThread.getInstance().post(new Runnable() {
                @Override
                public void run() {
                    for (OnSocketListener listener : listeners) {
                        listener.onError(Code.DISCONNECT, new YchsException(objects[0].toString()));
                    }
                }
            });
        }
    };

    private Emitter.Listener onErrorListener = new Emitter.Listener() {
        @Override
        public void call(final Object... objects) {
            Log.d(TAG, "call: error");
            SwitchToMainThread.getInstance().post(new Runnable() {
                @Override
                public void run() {
                    for (OnSocketListener listener : listeners) {
                        listener.onError(Code.OTHER_ERROR, new YchsException(objects[0].toString()));
                    }
                }
            });
        }
    };

    private Emitter.Listener onMessageListener = new Emitter.Listener() {
        @Override
        public void call(final Object... objects) {
            final SocketBean socketBean = new Gson().fromJson(objects[0].toString(), SocketBean.class);
            Log.i(TAG, "call: socket type===>>" + socketBean.getType());
            Log.i(TAG, "call: socket 消息===>>" + objects[0].toString());
            SwitchToMainThread.getInstance().post(new Runnable() {
                @Override
                public void run() {
                    for (OnSocketListener listener : listeners) {
                        if (socketBean.getResultCode() == 0) {
                            socketResponse(listener, socketBean.getType(), (JSONObject)objects[0]);
                        } else {
                            listener.onError(Code.MSG_ERROR, new YchsException(socketBean.getErrorMessage()));
                        }
                    }
                }
            });
        }
    };

    public abstract void socketResponse(OnSocketListener listener, String type, JSONObject json);

    public void addListener(OnSocketListener listener) {
        listeners.add(listener);
    }

    public void removeListener(OnSocketListener listener) {
        listeners.remove(listener);
    }

    public void removeAllListener() {
        listeners.clear();
    }
}
